/**
 * chenuu.com
 * 
 * @author  zhaoxu.com@gmail.com
 * @created 2012-11-14
 */
package com.chenuu.integration.dropbox.requester;

import java.io.IOException;

import javax.net.ssl.SSLException;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import com.chenuu.common.config.PropertiesConfigFactory;
import com.chenuu.integration.dropbox.config.DropboxConfig;
import com.chenuu.integration.dropbox.session.DropboxSession;
import com.chenuu.integration.httpclient.oauth.OAuthToken;
import com.chenuu.integration.httpclient.utils.RequestUtil;
import com.dropbox.client2.exception.DropboxException;
import com.dropbox.client2.exception.DropboxIOException;
import com.dropbox.client2.exception.DropboxSSLException;
import com.dropbox.client2.exception.DropboxServerException;

/**
 *
 */
public class DropboxRequester {
	
	private static DropboxConfig config = PropertiesConfigFactory.getConfig(DropboxConfig.class);

	private DropboxSession session;

	public DropboxRequester(DropboxSession session) {
		this.setSession(session);
	}

	public HttpResponse execute(RequestInfo requestInfo) throws DropboxException {

		// --- Step 1: Build Request
		HttpUriRequest request = this.buildRequest(requestInfo);

		// --- Step 2: Build OAuth Header
		request.addHeader("Authorization", buildOAuthHeader(session.getOAuthToken()));

		// --- Step 3: Execute Request
		return execute(request, -1);
	}

	protected HttpUriRequest buildRequest(RequestInfo requestInfo) throws DropboxException {

		// Build URI
		String target = RequestUtil.buildURI(requestInfo.getUri(), requestInfo.getParams());
		
		HttpUriRequest request = null;
		switch (requestInfo.getMethod()) {
		case GET:
			request = new HttpGet(target);
			break;
		case POST:
			request = new HttpPost(target);
			break;
		default:
			throw new IllegalArgumentException("Not Supported Request Method [" + requestInfo.getMethod() + "].");
		}

		return request;
	}

	private HttpResponse execute(HttpUriRequest request, int socketTimeoutOverrideMs)
			throws DropboxException {

		HttpClient client = session.getHttpClient();

		HttpParams reqParams = request.getParams();
		HttpConnectionParams.setSoTimeout(reqParams,
				socketTimeoutOverrideMs >= 0 ? socketTimeoutOverrideMs : config.getConnectionsTimeout());
		HttpConnectionParams.setConnectionTimeout(reqParams, config.getConnectionsTimeout());

		try {
			HttpResponse response = null;
			for (int retries = 0; response == null && retries < 5; retries++) {
				response = client.execute(request);
			}

			if (response == null) {
				// This is from that bug, and retrying hasn't fixed it.
				throw new DropboxIOException(
						"Apache HTTPClient encountered an error. No response, try again.");
			}

			int statusCode = response.getStatusLine().getStatusCode();
			if (statusCode != DropboxServerException._200_OK
					&& statusCode != DropboxServerException._206_PARTIAL_CONTENT) {
				throw new DropboxServerException(response);
			}

			return response;
		} catch (SSLException e) {
			throw new DropboxSSLException(e);
		} catch (IOException e) {
			// Quite common for network going up & down or the request being
			// cancelled, so don't worry about logging this
			throw new DropboxIOException(e);
		} catch (OutOfMemoryError e) {
			throw new DropboxException(e);
		}
	}

	private String buildOAuthHeader(OAuthToken token) {

		StringBuilder buf = new StringBuilder();
		buf.append("OAuth oauth_version=\"1.0\"");
		buf.append(", oauth_signature_method=\"PLAINTEXT\"");
		buf.append(", oauth_consumer_key=\"").append(config.getAppKey()).append("\"");

		String sig = config.getAppSecret() + "&";
		if (token != null) {
			buf.append(", oauth_token=\"").append(token.getKey()).append("\"");
			sig += token.getSecret();
		}
		buf.append(", oauth_signature=\"").append(sig).append("\"");

		// Note: Don't need nonce or timestamp since we do everything over SSL.
		return buf.toString();
	}

	public DropboxSession getSession() {
		return session;
	}

	public void setSession(DropboxSession session) {
		this.session = session;
	}

}
